package tom_io_nio.nio.demo;


import com.sun.corba.se.spi.activation.Server;
import com.sun.deploy.util.StringUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 网络多客户端聊天室
 * 功能1： 客户端通过Java NIO连接到服务端，支持多客户端的连接
 * 功能2：客户端初次连接时，服务端提示输入昵称，如果昵称已经有人使用，提示重新输入，如果昵称唯一，则登录成功，之后发送消息都需要按照规定格式带着昵称发送消息
 * 功能3：客户端登录后，发送已经设置好的欢迎信息和在线人数给客户端，并且通知其他客户端该客户端上线
 * 功能4：服务器收到已登录客户端输入内容，转发至其他登录客户端。
 *
 * TODO 客户端下线检测
 */
public class NioServer {

    private int port = 8080;
    private Charset charset = Charset.forName("UTF-8");

    //用来记录在线人数，以及昵称
    private static HashSet<String> users = new HashSet<>();

    private static String USER_EXIST = "系统提示：该昵称已经存在，请换一个昵称";

    //相当于自定义协议格式，与客户端协商好
    private static String USER_CONTENT_SPILIT = "#@#";

    private Selector selector;

    /**
     *
     * 启动服务端
     * **/
    public NioServer(int port) throws IOException {
        this.port = port;
        //打开通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定
        serverSocketChannel.bind(new InetSocketAddress(this.port));
        //设置非阻塞
        serverSocketChannel.configureBlocking(false);
        //获取选择器
        selector = Selector.open();
        //注册，开始接受请求
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务已启动，监听端口是：" + this.port);
    }

    /**
     * 监听请求
     * */
    public void listener() throws Exception{
        //死循环，此处线程数量可控
        while(true){
            //轮训，显示有多少等待请求
            int waitNum = selector.select();
            if(waitNum==0){
                continue;
            }
            //取出一个请求，可以通过这个方法，知道可用通道的集合
            Set<SelectionKey> selectionKeySet =  selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeySet.iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey =  iterator.next();
                process(selectionKey);
                iterator.remove();//处理一个，删除一个
            }
        }

    }


    /***
     * 处理方法
     * */
    public void process(SelectionKey key) throws IOException{
        //判断客户端已经可以交互了
        if(key.isAcceptable()){   //这是客户端和服务端的第一次交互，可以接收后注册
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            //设置为非阻塞模式
            client.configureBlocking(false);
            //注册选择器，并设置为读取模式，收到一个连接请求，然后起一个SocketChannel,
            //并注册到selector上，之后这个连接的数据，由这个SocketChannel处理。
            client.register(selector,SelectionKey.OP_READ);
            //将此对应的channel设置为准备接收其他客户端请求
            key.interestOps(SelectionKey.OP_ACCEPT);
            client.write(charset.encode("请输入你的昵称"));
        }
        //处理来自客户端的数据读取请求
        if(key.isReadable()){
            //返回该selectionKey对用的channel,其中有数据需要读取
            SocketChannel client = (SocketChannel)key.channel();
            //往缓冲区读数据
            ByteBuffer buff = ByteBuffer.allocate(1024);
            StringBuilder content = new StringBuilder();
            try{
                while(client.read(buff) > 0){
                    buff.flip();
                    content.append(charset.decode(buff));
                }
                //将此对应的channel设置为准备下一次接收数据
                key.interestOps(SelectionKey.OP_READ);
            }catch (IOException e){
                key.cancel();
                if(key.channel()!=null){
                    key.channel().close();
                }
            }
            if(content.length()>0){
                String[] arrayContent = content.toString().split(USER_CONTENT_SPILIT);
                //注册用户
                if(arrayContent != null && arrayContent.length ==1){
                    String nickName = arrayContent[0];
                    if(users.contains(nickName)){
                        client.write(charset.encode(USER_EXIST));
                    }else{
                        users.add(nickName);
                        int onlineCount = onlineCount();
                        String message = "欢迎 " + nickName + " 进入聊天室! 当前在线人数:" + onlineCount;
                        broadCast((SocketChannel)key.channel(), message);
                    }
                }
                if(arrayContent.length>1){
                    if(arrayContent[1] != null){
                        broadCast((SocketChannel)key.channel(), arrayContent[0] + ": " + arrayContent[1]);
                    }
                }
            }
        }
    }


    /**
     *
     * 统计在线人数  要是能检测下线，就不用这么统计了
     * **/
    public int onlineCount(){
        int res = 0;
        for(SelectionKey key : selector.keys()){
            Channel target = key.channel();
            if(target instanceof SocketChannel){
                res++;
            }
        }
        return res;
    }


    public void broadCast(SocketChannel client,String content) throws IOException {
        //广播数据到所有的SocketChannel
        for(SelectionKey key : selector.keys()){
            Channel targetChannel = key.channel();
            //如果client不为空,不回 发给此内容的客户端
            if(targetChannel instanceof  SocketChannel && targetChannel != client){
                SocketChannel target = (SocketChannel)targetChannel;
                target.write(charset.encode(content));
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new NioServer(8080).listener();
    }


}


















